package com.rainbow.common.core.toolkit;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.lang.NonNull;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * SpringBeanUtil工具类
 * 方便在容器中获取bean对象
 * @author 傅为地
 */
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
public class SpringBeanUtil implements ApplicationContextAware, BeanFactoryPostProcessor {
    /**
     * Spring应用上下文环境
     */
    private static volatile ApplicationContext applicationContext;

    /**
     * 实例化工厂
     */
    private static volatile ConfigurableListableBeanFactory beanFactory;

    /**
     * 实现ApplicationContextAware接口的回调方法，设置上下文环境
     *
     * @param applicationContext 上下文参数
     */
    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext){
        try {
            SpringBeanUtil.applicationContext = applicationContext;
        } catch (BeansException e) {
            log.error("SpringBeanUtil setApplicationContext error:", e);
        }
    }

    /**
     * 初始化bean工厂
     * @param configurableListableBeanFactory 配置工厂
     */
    @Override
    public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory configurableListableBeanFactory) {
        try {
            SpringBeanUtil.beanFactory = configurableListableBeanFactory;
        } catch (BeansException e) {
            log.error("SpringBeanUtil postProcessBeanFactory error:", e);
        }
    }

    /**
     * 获取上下文对象
     * @return 获取上下文对象
     */
    public static ApplicationContext getApplicationContext() {
        return SpringBeanUtil.applicationContext;
    }

    /**
     * 获取对象
     *
     * @param name bean名称
     * @return Object 一个以所给名字注册的bean的实例
     */
    public static Object getBean(String name) {
        try {
            return applicationContext.getBean(name);
        } catch (BeansException e) {
            log.error("SpringBeanUtil getBean error:", e);
        }
        return null;
    }


    /**
     * 获取类型为requiredType的对象
     * 如果bean不能被类型转换，相应的异常将会被抛出（BeanNotOfRequiredTypeException）
     *
     * @param <T> 泛型类型
     * @param requiredType 类型
     * @return Object 返回requiredType类型对象
     */
    public static <T> T getBean(Class<T> requiredType) {
        try {
            return applicationContext.getBean(requiredType);
        } catch (BeansException e) {
            log.error("SpringBeanUtil getBean error:", e);
        }
        return null;
    }

    /**
     * 获取类型为requiredType的对象
     * 如果bean不能被类型转换，相应的异常将会被抛出（BeanNotOfRequiredTypeException）
     *
     * @param <T> 泛型类型
     * @param name         bean注册名
     * @param requiredType 返回对象类型
     * @return Object 返回requiredType类型对象
     */
    public static <T> T getBean(String name, Class<T> requiredType) {
        try {
            return applicationContext.getBean(name, requiredType);
        } catch (BeansException e) {
            log.error("SpringBeanUtil getBean error:", e);
        }
        return null;

    }

    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean定义，则返回true
     *
     * @param name bean名称
     * @return 容器是否包含指定bean
     */
    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }

    /**
     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。
     * 如果与给定名字相应的bean定义没有被找到，将会抛出一个异常（NoSuchBeanDefinitionException）
     *
     * @param name 实例名称
     * @return 是否为单例
     */
    public static boolean isSingleton(String name) {
        try {
            return applicationContext.isSingleton(name);
        } catch (NoSuchBeanDefinitionException e) {
            log.error("SpringBeanUtil isSingleton error:", e);
        }
        return false;
    }

    /**
     * 获取注册的对象类型
     * @param name bean名称
     * @return Class 注册对象的类型
     */
    public static Class<?> getType(String name) {
        try {
            return applicationContext.getType(name);
        } catch (NoSuchBeanDefinitionException e) {
            log.error("SpringBeanUtil getType error:", e);
        }
        return null;
    }

    /**
     * 如果给定的bean名字在bean定义中有别名，则返回这些别名
     * @param name bean名称
     * @return 返回指定bean别名
     */
    public static String[] getAliases(String name) {
        try {
            return applicationContext.getAliases(name);
        } catch (NoSuchBeanDefinitionException e) {
            log.error("SpringBeanUtil getAliases error:", e);
        }
        return null;
    }

    /**
     * 找到某个类或接口的所有实例
     * @param type bean类型
     * @param <T> 泛型类型
     * @return 返回泛型类型bean
     */
    public static <T> List<T> getBeansOfType(Class<T> type) {
        return new ArrayList<>(beanFactory.getBeansOfType(type).values());
    }

    /**
     * 通过注解找到所有含注解的类
     * @param annotationType 注解类型
     * @return 返回含有指定注解的bean
     */
    public static List<Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) {
        return new ArrayList<>(beanFactory.getBeansWithAnnotation(annotationType).values());
    }


    /**
     * 获取指定类的在指定包下所有子类实例
     * @param superClass  父类类型
     * @param basePackage 扫描的包
     * @return 获取指定类的所有子类实例
     */
    public static <T> List<T> getAllSubclassInstances(@NonNull  Class<T> superClass,@NonNull String basePackage) {
        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
        provider.addIncludeFilter(new AssignableTypeFilter(superClass));
        Set<BeanDefinition> components = provider.findCandidateComponents(basePackage);
        List<T> result = new ArrayList<>();
        try {
            for (BeanDefinition component : components) {
                result.add(superClass.cast(getBean(Class.forName(component.getBeanClassName()))));
            }
        } catch (Exception error) {
            log.error("SpringBeanUtil getAllSubclassInstances error:", error);
        }
        return result;
    }

}